/*
    kOTF Open Trace Format - a library for generating execution traces.
    Copyright 2012 Daniel K. O.

    kOTF is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    kOTF is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with kOTF.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <kotf/Reader.hpp>

#include <cstring>
#include <errno.h>

#include <boost/regex.hpp>
#include <boost/filesystem.hpp>

#include <kotf/Session.hpp>
#include <kotf/Stream.hpp>

#include "SessionImpl.hpp"
#include "StreamImpl.hpp"

#include <sys/mman.h> // TODO: write a windows version (look at GLib)
#include <sys/stat.h>
#include <fcntl.h> // open()
#include <unistd.h> // close()


namespace kotf {
    namespace {
        uint64_t stoid(const string& s)
        {
            return std::stoull(s);
        }
    }

    SessionReader::SessionReader(const string& sessname)
    {
        // first, normalize filename and name
        boost::filesystem::path filename = sessname;
        if (filename.extension() != ".kotf")
            filename = sessname + ".kotf";

        string filenamestr = filename.string();
        name = filenamestr.substr(0, filenamestr.length()-5); // no .kotf


        std::ifstream sessfile(filename.string().c_str());
        if (!sessfile)
            throw InvalidSession(filename.string() + ": open failed");

        string line;

        if (!getline(sessfile, line) || line != SessionImpl::magic)
            throw InvalidSession(filename.string() + ": header mismatch; file does not seems to be valid");

        boost::regex assignment1("(\\w+) *= *([^\"]*) *");
        boost::regex assignment2("(\\w+) *[\\[] *(\\d+) *[\\]] *= *\"([^\"]*)\" *");
        
        bool have_start=false, have_finish = false;

        while (getline(sessfile, line)) {
            if (line.empty() || line[0] == '#') // ignore empty and comment lines
                continue;
            
            boost::match_results<string::const_iterator> match;
            
            if (regex_match(line, match, assignment1)) {
                // 1 = 2
                const string& record = match[1];
                const string& n = match[2];

                if (record == "startTime") {
                    startTime = stoull(n);
                    have_start = true;
                } else if (record == "finishTime") {
                    finishTime = stoull(n);
                    have_finish = true;
                } else
                    throw UnknownRecord(n);
            } else if (regex_match(line, match, assignment2)) {
                // 1[2] = 3
                const string& record = match[1];
                uint64_t id = stoid(match[2]);
                const string& n = match[3];

                if (record == "stream")
                    streams.insert(id, new StreamReader(name, n));
                else if (record == "state")
                    states[id] = n;
                else if (record == "counter")
                    counters[id] = n;
                else if (record == "channel")
                    channels[id] = n;
                else
                    throw UnknownRecord(n);
            }
        }

        if (!have_start || !have_finish)
            throw InvalidSession(filename.string()+": no start/finish times");

    }



    StreamReader::MMapFile::MMapFile(const string& fname) throw (OpenError, MmapError)
    {
        fd = open(fname.c_str(), O_RDONLY);
        if (fd < 0) {
            int e = errno;
            char buf[256];
            strerror_r(e, buf, sizeof buf);
            throw OpenError(fname + ": " + string(buf));
        }
        
        struct stat st;
        fstat(fd, &st);
        fsize = st.st_size;
        //size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
        ptr = mmap(0, fsize, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
        if (ptr == (void*)-1)
            throw MmapError(fname + ": mmap failed");
    }
    
    StreamReader::MMapFile::~MMapFile()
    {
        munmap(ptr, fsize);
    }
    
    size_t
    StreamReader::MMapFile::size() const
    {
        return fsize;
    }


    StreamReader::StreamReader(const string& sessname, const string& n) throw (InvalidStream)
    try :
        name(n),
        file(sessname + "-" + name + ".stream")
    {
        const Event *e = begin()-1;
        if (std::memcmp(StreamImpl::magic, e, sizeof(Event)))
            throw InvalidStream("stream " + name + ": header mismatch");
    }
    catch (Error& e) {
        throw InvalidStream(e.what());
    }

    size_t
    StreamReader::size() const
    {
        return file.size() / sizeof(Event) - 1; // ignore the header
    }
    
    StreamReader::const_iterator
    StreamReader::begin() const
    {
        return file.as<Event>() + 1;
    }

    StreamReader::const_iterator
    StreamReader::end() const
    {
        return file.as<Event>() + 1 + size();
    }


}
